Skip to content

《C++面向对象程序设计(甲)》期末试卷B (精选01)

注意:答案直接写在答题纸上,答在试卷上无效,考试后答题纸和试卷一同上交

一、单项选择题(每题 2 分,总计 20 分)

  1. 面向对象程序设计中的数据隐藏指的是 ( )
    • A. 输入数据必须输入保密口令
    • B. 数据经过加密处理
    • C. 对象内部数据和代码合并在一起
    • D. 对象内部数据结构的不可访问性
查看答案与解析

答案:D

解析: 本题考查面向对象程序设计中的数据隐藏概念。

第一步:理解数据隐藏的含义 数据隐藏是面向对象程序设计的基本特征之一,指的是对象内部的数据结构(私有成员)对外部是不可见的,外部只能通过对象提供的公有接口(成员函数)来访问和操作数据。

第二步:逐一分析选项

  • A 选项:输入数据必须输入保密口令——这是安全性措施,不是数据隐藏的核心含义。
  • B 选项:数据经过加密处理——加密是数据安全技术,与面向对象的数据隐藏无关。
  • C 选项:对象内部数据和代码合并在一起——这描述的是封装的概念,但不准确。封装是将数据和操作数据的方法绑定在一起,而数据隐藏强调的是"不可访问性"。
  • D 选项:对象内部数据结构的不可访问性——正确,这正是数据隐藏的核心:外部不能直接访问对象的内部数据,只能通过公有接口间接访问。

方法总结:数据隐藏 = 私有成员对外不可见 + 通过公有接口访问。


难度: ⭐ 考点: #数据隐藏 #面向对象基本特征 #封装

💡 学习锦囊

📖 相关公式与知识点:

  • 面向对象的三大基本特征:封装、继承、多态
  • 封装 = 数据隐藏 + 公有接口
  • 数据隐藏通过 privateprotected 访问控制实现
  • 公有接口通过 public 成员函数提供

思路分析

数据隐藏的关键词是"不可访问性"——外部代码不能直接读写对象的私有数据成员,必须通过类提供的公有方法。

易错点

  • 容易将"数据隐藏"与"加密"混淆——加密是安全技术,数据隐藏是设计原则
  • 容易将"封装"等同于"数据隐藏"——封装包含数据隐藏,但范围更广
🔄 举一反三
  1. 下列关于封装的描述中,正确的是 ( )
    • A. 封装就是将所有数据成员都设为 private
    • B. 封装是将数据和操作数据的方法绑定在一起,并隐藏内部实现细节
    • C. 封装要求所有成员函数都必须是 public 的
    • D. 封装就是数据隐藏
    查看练习答案与解析

    答案:B 解析:封装是将数据(属性)和操作数据的方法(行为)绑定在一起形成一个类,同时隐藏内部实现细节,只暴露必要的接口。A 错误因为封装不一定所有数据都是 private;C 错误因为成员函数可以是 private 或 protected;D 错误因为数据隐藏只是封装的一部分。

  1. 一个内联函数 Fun,使用 int 类型的参数,求其平方并返回,返回值也为 int 类型,下列定义正确的是 ( )
    • A. int Fun(int x){return x*x;}
    • B. inline int Fun(int x){return x*x;}
    • C. int Fun inline(int x){return x*x;}
    • D. int Fun(int x){inline return x*x;}
查看答案与解析

答案:B

解析: 本题考查内联函数的定义语法。

第一步:回顾内联函数的定义方式 内联函数使用关键字 inline 来声明,语法格式为:

inline 返回值类型 函数名(参数列表) { 函数体 }

第二步:逐一分析选项

  • A 选项:int Fun(int x){return x*x;}——没有 inline 关键字,只是普通函数定义。
  • B 选项:inline int Fun(int x){return x*x;}——正确,inline 在返回值类型之前,符合标准语法。
  • C 选项:int Fun inline(int x){return x*x;}——inline 放在函数名和参数列表之间,语法错误。
  • D 选项:int Fun(int x){inline return x*x;}——inline 放在函数体内部,语法错误。

第三步:内联函数的工作原理 内联函数在编译时会在调用处展开函数体代码,避免了函数调用的开销(参数压栈、跳转等),适用于短小、频繁调用的函数。


难度: ⭐ 考点: #内联函数 #inline关键字 #函数定义语法

💡 学习锦囊

📖 相关公式与知识点:

  • 内联函数定义:inline 返回类型 函数名(参数) { 函数体 }
  • 内联函数适用于短小(通常 1-5 行)、频繁调用的函数
  • 类体内定义的成员函数自动成为内联函数
  • inline 是对编译器的建议,编译器可能忽略

思路分析

记住 inline 关键字的正确位置:在返回值类型之前,即 inline int Fun(...)

易错点

  • inline 放在函数体内部(如 D 选项)
  • 认为类外定义的成员函数加 inline 也必须放在类声明中——实际上在类外定义时加 inline 也可以
🔄 举一反三
  1. 下列关于内联函数的描述中,错误的是 ( )
    • A. 内联函数在编译时进行代码展开
    • B. 内联函数可以减少函数调用的开销
    • C. 所有函数都应该定义为内联函数以提高效率
    • D. 递归函数通常不适合定义为内联函数
    查看练习答案与解析

    答案:C 解析:内联函数会导致代码膨胀(每个调用点都展开一份代码),因此只适合短小函数。如果所有函数都定义为内联函数,可执行文件会变得非常大,反而降低效率。递归函数无法在编译时确定展开次数,不适合内联。

  1. 下面关于重载函数的叙述中正确的是 ( )
    • A. 重载函数必须具有不同的返回值类型
    • B. 重载函数的形参个数必须不同
    • C. 重载函数必须有不同的形参列表
    • D. 重载函数的函数名可以不同
查看答案与解析

答案:C

解析: 本题考查函数重载的判定条件。

第一步:回顾函数重载的定义 函数重载是指在同一个作用域内,多个函数具有相同的函数名,但形参列表(参数个数、类型或顺序)不同。

第二步:逐一分析选项

  • A 选项:重载函数必须具有不同的返回值类型——错误。返回值类型不能作为重载的区分条件,因为调用函数时可以不使用返回值。
  • B 选项:重载函数的形参个数必须不同——错误。形参个数可以相同,只要类型或顺序不同即可。例如 void f(int)void f(double) 参数个数相同但类型不同,构成重载。
  • C 选项:重载函数必须有不同的形参列表——正确。这是重载的核心条件。
  • D 选项:重载函数的函数名可以不同——错误。重载的前提是函数名相同。

第三步:重载的判定规则 编译器根据函数调用时实参的类型和数量,匹配对应的重载版本。仅返回值类型不同不构成重载。


难度: ⭐ 考点: #函数重载 #形参列表 #重载判定条件

💡 学习锦囊

📖 相关公式与知识点:

  • 重载条件:函数名相同 + 形参列表不同(个数/类型/顺序)
  • 返回值类型不能区分重载
  • 形参的 const 修饰可以区分重载(如 void f(int&)void f(const int&)
  • 重载解析:编译器根据实参匹配最佳重载版本

思路分析

记住口诀:"同名不同参"——函数名相同,参数列表不同。

易错点

  • 认为返回值类型不同就可以重载——这是最常见的错误
  • 忘记 const 成员函数也可以构成重载
🔄 举一反三
  1. 下列函数中,能与 void f(int a) 构成重载的是 ( )
    • A. int f(int a)
    • B. void f(int a, int b)
    • C. void g(int a)
    • D. int f(double a)
    查看练习答案与解析

    答案:B 和 D 解析:B 参数个数不同(1 个 vs 2 个),构成重载;D 参数类型不同(int vs double),构成重载。A 仅返回值类型不同,不构成重载;C 函数名不同(g vs f),不构成重载。

  1. 下列关于纯虚函数的描述中,错误的是 ( )
    • A. 在基类中只有声明,通常没有函数体定义
    • B. 可以使用包含纯虚函数的类来创建对象
    • C. 当需要使用包含纯虚函数的基类的派生类创建对象时,须在派生类中给出该函数定义
    • D. 包含纯虚函数的类称为抽象类
查看答案与解析

答案:B

解析: 本题考查纯虚函数和抽象类的概念。

第一步:回顾纯虚函数的定义 纯虚函数是在基类中声明但不实现的虚函数,语法为:virtual 返回类型 函数名(参数) = 0;

第二步:逐一分析选项

  • A 选项:在基类中只有声明,通常没有函数体定义——正确。纯虚函数在基类中通过 =0 声明,通常不提供函数体(但 C++ 允许在类外为纯虚函数提供定义)。
  • B 选项:可以使用包含纯虚函数的类来创建对象——错误。包含纯虚函数的类是抽象类,不能实例化(创建对象)。
  • C 选项:派生类需要给出纯虚函数的定义才能创建对象——正确。派生类必须实现(override)所有纯虚函数,才能成为具体类并创建对象。
  • D 选项:包含纯虚函数的类称为抽象类——正确。

第三步:抽象类的特性 抽象类不能实例化,但可以定义指针和引用,用于实现多态。


难度: ⭐ 考点: #纯虚函数 #抽象类 #虚函数

💡 学习锦囊

📖 相关公式与知识点:

  • 纯虚函数语法:virtual void func() = 0;
  • 抽象类 = 包含至少一个纯虚函数的类
  • 抽象类不能实例化(不能创建对象)
  • 派生类必须实现所有纯虚函数才能实例化
  • 抽象类可以定义指针和引用

思路分析

看到"纯虚函数"→ 想到"=0"→ 想到"抽象类"→ 想到"不能创建对象"。

易错点

  • 混淆虚函数和纯虚函数——虚函数有函数体,纯虚函数没有
  • 认为抽象类完全不能使用——实际上可以定义指针/引用
🔄 举一反三
  1. 下列关于抽象类的描述中,正确的是 ( )
    • A. 抽象类中不能定义构造函数
    • B. 抽象类不能定义数据成员
    • C. 抽象类可以作为基类被继承
    • D. 抽象类中的所有函数都必须是纯虚函数
    查看练习答案与解析

    答案:C 解析:抽象类可以作为基类被继承,这是它的主要用途——定义接口规范。A 错误,抽象类可以有构造函数(供派生类调用);B 错误,抽象类可以有数据成员;D 错误,抽象类中可以有非纯虚函数(普通成员函数)。

  1. 下列关于析构函数的描述中正确的是 ( )
    • A. 析构函数可以重载
    • B. 析构函数可以是虚函数
    • C. 析构函数名与类名相同
    • D. 析构函数的返回类型为 void
查看答案与解析

答案:B

解析: 本题考查析构函数的特性。

第一步:回顾析构函数的基本特征 析构函数在对象生命周期结束时自动调用,用于释放资源。其特征包括:

  • 函数名:~类名
  • 无返回值类型(连 void 也不写)
  • 无参数,因此不能重载
  • 一个类只有一个析构函数

第二步:逐一分析选项

  • A 选项:析构函数可以重载——错误。析构函数没有参数,无法重载。
  • B 选项:析构函数可以是虚函数——正确。将基类的析构函数声明为虚函数,可以确保通过基类指针删除派生类对象时,正确调用派生类的析构函数。
  • C 选项:析构函数名与类名相同——错误。析构函数名是 ~类名,不是类名本身。
  • D 选项:析构函数的返回类型为 void——错误。析构函数没有返回类型。

第三步:虚析构函数的重要性 当通过基类指针删除派生类对象时,如果析构函数不是虚函数,只会调用基类的析构函数,可能导致派生类资源泄漏。


难度: ⭐ 考点: #析构函数 #虚析构函数 #资源管理

💡 学习锦囊

📖 相关公式与知识点:

  • 析构函数格式:~类名() { ... }
  • 析构函数无参数、无返回值、不可重载
  • 虚析构函数:virtual ~类名() { ... }
  • 基类析构函数建议声明为虚函数
  • 析构函数调用顺序:先派生类析构,后基类析构

思路分析

析构函数的关键特征:~ 开头、无参、无返回值、唯一。虚析构函数是多态正确释放资源的关键。

易错点

  • 认为析构函数名与类名相同——实际是 ~类名
  • 忘记将基类析构函数声明为虚函数——导致派生类资源泄漏
🔄 举一反三
  1. 下列代码的输出结果是什么?
    cpp
    class Base {
    public:
        ~Base() { cout << "Base destructor" << endl; }
    };
    class Derived : public Base {
    public:
        ~Derived() { cout << "Derived destructor" << endl; }
    };
    int main() {
        Base* p = new Derived();
        delete p;
        return 0;
    }
    查看练习答案与解析

    答案:只输出 Base destructor解析:因为 Base 的析构函数不是虚函数,通过基类指针 delete 时只调用 Base 的析构函数,Derived 的析构函数不会被调用。将 ~Base() 改为 virtual ~Base() 后,输出为 Derived destructor 然后 Base destructor

  1. 下列关于纯虚函数的描述中,正确的是 ( )
    • A. 纯虚函数是一种特殊的虚函数,它是个空函数
    • B. 具有纯虚函数的类称为虚基类
    • C. 一个基类中说明有纯虚函数,其派生类一定要实现该纯虚函数
    • D. 具有纯虚函数的类不能创建类对象
查看答案与解析

答案:D

解析: 本题考查纯虚函数和抽象类的正确理解。

第一步:逐一分析选项

  • A 选项:纯虚函数是个空函数——错误。纯虚函数是"没有实现"的函数(=0),不是"空函数"(空函数有函数体 {})。
  • B 选项:具有纯虚函数的类称为虚基类——错误。具有纯虚函数的类称为"抽象类";"虚基类"是解决菱形继承问题的概念(virtual 继承)。
  • C 选项:派生类一定要实现该纯虚函数——错误。派生类可以不实现纯虚函数,但此时派生类也是抽象类,不能实例化。
  • D 选项:具有纯虚函数的类不能创建类对象——正确。这是抽象类的核心特性。

第二步:区分关键概念

  • 纯虚函数 ≠ 空函数:virtual void f() = 0;(纯虚函数)vs virtual void f() {}(空实现的虚函数)
  • 抽象类 ≠ 虚基类:抽象类(含纯虚函数)vs 虚基类(virtual 继承的基类)

难度: ⭐ 考点: #纯虚函数 #抽象类 #虚基类

💡 学习锦囊

📖 相关公式与知识点:

  • 纯虚函数:virtual void f() = 0;——只有声明,没有实现
  • 空函数:virtual void f() {}——有实现,但函数体为空
  • 抽象类:包含纯虚函数的类,不能实例化
  • 虚基类:使用 virtual 继承的基类,解决菱形继承

思路分析

区分三个易混淆概念:纯虚函数(=0)、抽象类(含纯虚函数)、虚基类(virtual 继承)。

易错点

  • 混淆"抽象类"和"虚基类"——两个完全不同的概念
  • 认为派生类必须实现纯虚函数——可以不实现,但自身也成为抽象类
🔄 举一反三
  1. 下列关于虚基类的描述中,正确的是 ( )
    • A. 虚基类就是包含纯虚函数的类
    • B. 虚基类用于解决多重继承中的二义性问题
    • C. 虚基类不能有数据成员
    • D. 虚基类的构造函数不会被调用
    查看练习答案与解析

    答案:B 解析:虚基类(virtual base class)用于解决菱形继承中基类成员的多份拷贝问题。A 错误,包含纯虚函数的是抽象类;C 错误,虚基类可以有数据成员;D 错误,虚基类的构造函数由最派生类负责调用。

  1. 复制初始化构造函数的作用是 ( )
    • A. 进行数据类型的转换
    • B. 用对象调用成员函数
    • C. 用对象初始化对象
    • D. 用一般类型的数据初始化对象
查看答案与解析

答案:C

解析: 本题考查复制(拷贝)构造函数的用途。

第一步:回顾拷贝构造函数的定义 拷贝构造函数是用一个已存在的同类对象来初始化新创建的对象。其典型声明为:

cpp
ClassName(const ClassName& other);

第二步:逐一分析选项

  • A 选项:进行数据类型的转换——这是转换构造函数的作用(单参数非拷贝构造函数)。
  • B 选项:用对象调用成员函数——这是普通成员函数调用的作用。
  • C 选项:用对象初始化对象——正确。这正是拷贝构造函数的用途。
  • D 选项:用一般类型的数据初始化对象——这是普通构造函数或转换构造函数的作用。

第三步:拷贝构造函数的调用场景

  1. 用已有对象初始化新对象:ClassName obj2 = obj1;
  2. 函数参数按值传递对象时
  3. 函数返回值为对象时

难度: ⭐ 考点: #拷贝构造函数 #对象初始化 #深拷贝与浅拷贝

💡 学习锦囊

📖 相关公式与知识点:

  • 拷贝构造函数声明:ClassName(const ClassName& obj);
  • 默认拷贝构造函数执行浅拷贝(逐成员复制)
  • 当类包含指针成员时,需要自定义深拷贝
  • 调用场景:初始化新对象、值传递参数、值返回对象

思路分析

拷贝构造函数 = "用同类对象初始化新对象"。关键词是"初始化"(区别于"赋值")。

易错点

  • 混淆拷贝构造函数和赋值运算符——拷贝构造是初始化,赋值是已有对象的赋值
  • 忘记 const 引用参数——应该是 const ClassName&
🔄 举一反三
  1. 下列哪种情况不会调用拷贝构造函数? ( )
    • A. MyClass obj2 = obj1;
    • B. void func(MyClass obj); 调用 func(obj1);
    • C. obj2 = obj1;(obj2 已存在)
    • D. return obj1;(函数返回值)
    查看练习答案与解析

    答案:C 解析:C 是赋值操作,调用的是赋值运算符 operator=,不是拷贝构造函数。A 是初始化,调用拷贝构造;B 是值传递参数,调用拷贝构造;D 是返回值,调用拷贝构造。

  1. 所谓多态性是指 ( )
    • A. 不同的对象调用不同名称的函数
    • B. 不同的对象调用相同名称的函数
    • C. 一个对象调用不同名称的函数
    • D. 一个对象调用不同名称的对象
查看答案与解析

答案:B

解析: 本题考查面向对象中多态性的概念。

第一步:理解多态的含义 多态(Polymorphism)指的是同一个接口(函数名),不同的对象调用时表现出不同的行为。即"一个接口,多种实现"。

第二步:逐一分析选项

  • A 选项:不同对象调用不同名称的函数——这只是普通的函数调用,不是多态。
  • B 选项:不同的对象调用相同名称的函数——正确。通过虚函数机制,基类指针/引用调用同名函数时,根据实际对象类型执行不同的函数版本。
  • C 选项:一个对象调用不同名称的函数——普通成员函数调用。
  • D 选项:一个对象调用不同名称的对象——语法不通。

第三步:多态的两种形式

  • 编译时多态(静态多态):函数重载、运算符重载、模板
  • 运行时多态(动态多态):虚函数 + 基类指针/引用

难度: ⭐ 考点: #多态性 #虚函数 #动态绑定

💡 学习锦囊

📖 相关公式与知识点:

  • 多态 = 同一接口,不同实现
  • 静态多态:函数重载、模板(编译时确定)
  • 动态多态:虚函数 + 基类指针/引用(运行时确定)
  • 动态多态三要素:继承、虚函数、基类指针/引用

思路分析

多态的核心:"同名函数,不同行为"。通过基类指针调用虚函数时,实际执行的是派生类的版本。

易错点

  • 认为函数重载就是多态的全部——重载只是静态多态
  • 忘记多态需要基类指针/引用——直接通过对象调用不会触发多态
🔄 举一反三
  1. 下列代码的输出结果是什么?
    cpp
    class Base {
    public:
        virtual void show() { cout << "Base" << endl; }
    };
    class Derived : public Base {
    public:
        void show() { cout << "Derived" << endl; }
    };
    int main() {
        Base b; Derived d;
        Base* p = &b; p->show();
        p = &d; p->show();
        return 0;
    }
    查看练习答案与解析

    答案:先输出 Base,再输出 Derived解析show() 是虚函数,通过基类指针调用时发生动态绑定。p 指向 b 时调用 Base::show(),指向 d 时调用 Derived::show()。这就是运行时多态。

  1. 要将类 A 说明是类 B 的虚基类,正确的描述是 ( )
    • A. class virtual B : public A
    • B. class B : virtual public A
    • C. virtual class B : public A
    • D. class B : public A virtual
查看答案与解析

答案:B

解析: 本题考查虚基类的声明语法。

第一步:回顾虚基类的语法 虚继承使用 virtual 关键字,放在继承方式之前:

cpp
class 派生类 : virtual 继承方式 基类 { ... };

第二步:逐一分析选项

  • A 选项:class virtual B : public A——virtual 位置错误,不能放在 class 关键字之后。
  • B 选项:class B : virtual public A——正确。virtual 在继承方式 public 之前。
  • C 选项:virtual class B : public A——virtual 位置错误。
  • D 选项:class B : public A virtual——virtual 位置错误,不能放在最后。

第三步:虚基类的作用 虚基类用于解决菱形继承(钻石继承)问题,确保最终派生类中只有一份基类成员的拷贝。


难度: ⭐ 考点: #虚基类 #虚继承 #菱形继承

💡 学习锦囊

📖 相关公式与知识点:

  • 虚继承语法:class B : virtual public A { };
  • 虚基类解决菱形继承中的二义性和数据冗余
  • 虚基类的构造函数由最派生类负责调用
  • virtual 关键字在继承方式之前

思路分析

记住语法格式:class 派生类 : virtual 继承方式 基类virtual 在继承方式前面。

易错点

  • virtual 的位置放错——必须在继承方式之前
  • 混淆虚基类和抽象类——虚基类用 virtual 继承,抽象类含纯虚函数
🔄 举一反三
  1. 在菱形继承结构中,如果不使用虚基类,最终派生类中会包含几份基类成员的拷贝? ( )
    • A. 1 份
    • B. 2 份
    • C. 3 份
    • D. 4 份
    查看练习答案与解析

    答案:B 解析:在典型的菱形继承中(B 和 C 都继承 A,D 同时继承 B 和 C),如果不使用虚基类,D 中会包含 2 份 A 的成员拷贝(分别来自 B 和 C 路径)。使用虚基类后,D 中只有 1 份 A 的成员拷贝。

  1. 标准模板库(STL)所涉及的 4 个最主要的基本组件是 ( )
    • A. 容器、迭代器、算法、函数模板
    • B. 类模板、运算符重载函数、容器、算法
    • C. 容器、算法、迭代器、函数对象
    • D. 类、对象、迭代器、函数
查看答案与解析

答案:C

解析: 本题考查 STL(Standard Template Library)的核心组件。

第一步:回顾 STL 的四大组件 STL 由以下四个核心组件构成:

  1. 容器(Containers):存储数据的模板类,如 vectorlistmap
  2. 算法(Algorithms):操作数据的模板函数,如 sortfindcopy
  3. 迭代器(Iterators):连接容器和算法的"胶水",提供遍历容器的方法
  4. 函数对象(Functors):重载了 operator() 的类对象,可作为算法的策略参数

第二步:逐一分析选项

  • A 选项:将"函数对象"误写为"函数模板"——错误。
  • B 选项:类模板和运算符重载不是 STL 的核心组件——错误。
  • C 选项:容器、算法、迭代器、函数对象——正确。
  • D 选项:类、对象是面向对象的基本概念,不是 STL 组件——错误。

难度: ⭐ 考点: #STL #标准模板库 #容器 #迭代器 #算法

💡 学习锦囊

📖 相关公式与知识点:

  • STL 四大组件:容器、算法、迭代器、函数对象
  • 常用容器:vector(动态数组)、list(双向链表)、map(键值对)、set(集合)
  • 常用算法:sortfindfor_eachcopy
  • 迭代器类型:输入、输出、前向、双向、随机访问
  • 函数对象:重载 operator() 的类

思路分析

STL = 容器(存数据)+ 算法(处理数据)+ 迭代器(遍历数据)+ 函数对象(定制行为)。

易错点

  • 将"函数对象"记成"函数模板"——函数模板是泛型编程的基础,但不是 STL 四大组件之一
  • 忘记迭代器是独立组件——迭代器是容器和算法之间的桥梁
🔄 举一反三
  1. 下列关于 STL 容器的描述中,错误的是 ( )
    • A. vector 支持随机访问
    • B. list 支持随机访问
    • C. map 存储键值对
    • D. set 中的元素自动排序
    查看练习答案与解析

    答案:B 解析list 是双向链表,不支持随机访问(不能使用 [] 运算符),只能通过迭代器顺序访问。vector 是动态数组,支持随机访问;map 存储键值对并按键自动排序;set 存储唯一元素并自动排序。

二、程序填空题(每空 2 分,总计 20 分)

  1. 请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为:
2,1
4,3
cpp
#include <iostream>
using namespace std;

class A
{
    int a;
public:
    A(int i = 0) { a = i; }
    int Geta() { return a; }
};

class B
{
    A a;
    int b;
public:
    B(int i = 0, int j = 0) : __(1)__ {}
    void display() { cout << a.Geta() << ',' << b << endl; }
};

int main()
{
    B b[2] = { B(1, 2), B(3, 4) };
    for (int i = 0; i < 2; i++)
        __(2)__
}
查看答案与解析

答案: (1) a(j), b(i) (2) b[i].display()

解析:

第 (1) 空分析:

  • 类 B 中包含一个 A 类型的成员对象 a 和一个 int 类型的成员 b
  • 在初始化列表中,需要用参数 j 初始化成员对象 a(调用 A 的构造函数 A(j)),用参数 i 初始化成员 b
  • 初始化列表语法:B(int i, int j) : a(j), b(i) {},其中 a 是 A 类成员,用 j 初始化;b 是 int 成员,用 i 初始化。

第 (2) 空分析:

  • 循环遍历数组 b 的两个元素,需要调用 display() 输出结果。
  • 语法:b[i].display();

难度: ⭐⭐ 考点: #成员初始化列表 #对象数组成员 #组合关系

💡 学习锦囊

📖 相关公式与知识点:

  • 成员初始化列表语法:构造函数(参数) : 成员1(值1), 成员2(值2) { }
  • 对象数组成员:ClassName arr[2] = { obj1, obj2 };
  • 成员对象必须在初始化列表中初始化(因为成员对象的构造先于包含类的构造体)

思路分析

看到初始化列表中的填空 → 思考哪些成员需要初始化 → 对象成员(A 类)和基本类型成员(int b)。

易错点

  • 初始化列表顺序应与成员声明顺序一致
  • 忘记对象成员必须在初始化列表中初始化
🔄 举一反三
  1. 下列代码的输出结果是什么?
    cpp
    class X {
        int val;
    public:
        X(int v) : val(v) { cout << "X(" << val << ") "; }
    };
    class Y {
        X x;
    public:
        Y(int a, int b) : x(a) { cout << "Y(" << b << ")"; }
    };
    int main() { Y y(10, 20); return 0; }
    查看练习答案与解析

    答案X(10) Y(20)解析:创建 Y 对象时,先执行初始化列表中的 x(a)(即 X(10)),输出 X(10),然后执行 Y 构造函数体,输出 Y(20)。成员对象先于包含类构造。

  1. 下面程序中 A 是抽象类。请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为:
B1 called
B2 called
cpp
#include <iostream>
using namespace std;

class A
{
public:
    __(3)__
};

class B1 : public A
{
public:
    void display() { cout << "B1 called" << endl; }
};

class B2 : public A
{
public:
    void display() { cout << "B2 called" << endl; }
};

void show(__(4)__)
{
    p->display();
}

int main()
{
    B1 b1;
    B2 b2;
    A* p[2] = { &b1, &b2 };
    for (int i = 0; i < 2; i++)
        show(p[i]);
}
查看答案与解析

答案: (3) virtual void display() = 0; (4) A *p

解析:

第 (3) 空分析:

  • 题目说明"A 是抽象类",抽象类必须包含至少一个纯虚函数。
  • 派生类 B1、B2 都实现了 display() 函数,说明基类 A 中声明了纯虚函数 display()
  • 纯虚函数语法:virtual void display() = 0;

第 (4) 空分析:

  • show 函数体中使用了 p->display(),说明参数 p 是一个指针。
  • main 中调用 show(p[i])p[i] 的类型是 A*
  • 因此参数类型应为 A*,即 void show(A *p)

难度: ⭐⭐ 考点: #纯虚函数 #抽象类 #多态 #基类指针

💡 学习锦囊

📖 相关公式与知识点:

  • 纯虚函数声明:virtual 返回类型 函数名(参数) = 0;
  • 抽象类必须包含至少一个纯虚函数
  • 通过基类指针实现运行时多态
  • 基类指针可以指向派生类对象

思路分析

抽象类 → 纯虚函数 = 0 → 派生类实现 → 基类指针调用 → 多态。

易错点

  • 忘记 = 0 的语法——纯虚函数必须有 = 0
  • 参数类型写成 B1*B2*——应该用基类指针 A* 实现通用性
🔄 举一反三
  1. 如果 A 类中还有一个纯虚函数 virtual void show() = 0;,而 B1 没有实现它,B1 能实例化吗?
    查看练习答案与解析

    答案:不能 解析:派生类必须实现基类的所有纯虚函数才能成为具体类(可实例化)。如果 B1 没有实现 show(),B1 也是抽象类,不能创建 B1 对象。

  1. 请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为:
Name:王小明
Grade:90
cpp
#include <iostream>
#include <cstring>

class Person
{
    char name[20];
public:
    Person(char* s) { strcpy(name, s); }
    void display() { cout << "Name:" << name << endl; }
};

class Student : public Person
{
    int grade;
public:
    Student(char* s, int g) : __(5)__ { grade = g; }
    void display()
    {
        __(6)__
        cout << "Grade:" << grade << endl;
    }
};

int main()
{
    Student s("王小明", 90);
    s.display();
}
查看答案与解析

答案: (5) Person(s) (6) Person::display();

解析:

第 (5) 空分析:

  • Student 继承自 PersonStudent 构造函数需要初始化基类 Person
  • Person 的构造函数需要一个 char* 参数,Student 构造函数接收了 char* s
  • 在初始化列表中调用基类构造函数:Person(s)

第 (6) 空分析:

  • Student::display() 需要先输出姓名(基类功能),再输出成绩。
  • 调用基类的 display() 函数:Person::display();
  • 使用作用域限定符 :: 明确指定调用基类版本。

难度: ⭐⭐ 考点: #继承 #基类构造调用 #作用域限定符

💡 学习锦囊

📖 相关公式与知识点:

  • 派生类构造函数初始化列表调用基类构造:Student(...) : Person(s) { }
  • 调用基类被覆盖的成员函数:Person::display();
  • 派生类对象构造顺序:基类 → 成员对象 → 派生类本身
  • 派生类对象析构顺序:派生类 → 成员对象 → 基类(与构造相反)

思路分析

派生类构造 → 先初始化基类(初始化列表)→ 派生类中调用基类方法 → 使用 基类名::方法名()

易错点

  • 在构造函数体内给基类成员赋值——基类构造应在初始化列表中调用
  • 直接写 display() 而不加 Person::——会递归调用自身
🔄 举一反三
  1. 下列代码的输出结果是什么?
    cpp
    class Base {
    public:
        Base() { cout << "B "; }
        void show() { cout << "Base::show "; }
    };
    class Derived : public Base {
    public:
        Derived() { cout << "D "; }
        void show() { cout << "Derived::show "; }
    };
    int main() {
        Derived d;
        d.show();
        d.Base::show();
        return 0;
    }
    查看练习答案与解析

    答案B D Derived::show Base::show解析:构造 Derived 对象时先调用 Base 构造输出 B,再执行 Derived 构造体输出 Dd.show() 调用 Derived 版本输出 Derived::showd.Base::show() 通过作用域限定符调用基类版本输出 Base::show

  1. 请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为 5
cpp
#include <iostream>
using namespace std;

class Integer
{
    int x;
public:
    Integer(int a = 0) { x = a; }
    void display() { cout << x << endl; }
    __(7)__
};

Integer Max(Integer a, Integer b)
{
    if (__(8)__)
        return a;
    return b;
}

int main()
{
    Integer a(3), b(5), c;
    c = Max(a, b);
    c.display();
}
查看答案与解析

答案: (7) friend Integer Max(Integer a, Integer b); (8) a.x > b.x

解析:

第 (7) 空分析:

  • Max 函数在类外定义,但需要访问 Integer 的私有成员 x
  • 因此 Max 需要被声明为 Integer 的友元函数。
  • 友元函数声明语法:friend 返回类型 函数名(参数列表);
  • 即:friend Integer Max(Integer a, Integer b);

第 (8) 空分析:

  • Max 函数需要比较两个 Integer 对象中 x 的值。
  • 由于 Max 是友元函数,可以直接访问私有成员 x
  • 比较表达式:a.x > b.x

难度: ⭐⭐ 考点: #友元函数 #私有成员访问 #运算符重载

💡 学习锦囊

📖 相关公式与知识点:

  • 友元函数声明:friend 返回类型 函数名(参数);
  • 友元函数可以访问类的私有和保护成员
  • 友元函数不是类的成员函数
  • 友元关系是单向的、不可传递的、不可继承的

思路分析

类外函数需要访问私有成员 → 声明为友元 → friend 关键字 → 友元函数可直接访问 a.x

易错点

  • 忘记友元函数不是成员函数——不需要通过对象调用
  • 友元声明放在类内部的任何位置都可以(public/private 无影响)
🔄 举一反三
  1. 如果将 Max 改为成员函数,应该如何修改代码?
    查看练习答案与解析

    答案:将 Max 改为成员函数:

    cpp
    Integer Max(Integer b) {
        if (x > b.x) return *this;
        return b;
    }

    调用方式改为 c = a.Max(b);。成员函数只需要一个参数(另一个操作数是 this),可以直接访问自己的 x 和参数的 x(同类对象间可以访问私有成员)。

  1. 请在下面的横线处填上适当内容,以使类的定义完整。
cpp
class Array
{
    int* ptr;
    int size;
public:
    Array() { size = 0; ptr = NULL; }
    Array(int n) { size = n; ptr = new int[size]; }
    Array(__(9)__)  // 复制初始化构造函数
    {
        size = a.size;
        ptr = new int[size];
        for (int i = 0; i < size; i++)
            __(10)__;  // 将源对象的动态数组内容复制到目标对象
    }
};
查看答案与解析

答案: (9) const Array &a (10) ptr[i] = a.ptr[i]

解析:

第 (9) 空分析:

  • 这是拷贝构造函数的声明,参数应为同类对象的引用。
  • 标准格式:Array(const Array &a)
  • const 确保不修改源对象,& 引用避免无限递归(值传递会再次调用拷贝构造)。

第 (10) 空分析:

  • 需要将源对象的动态数组内容逐元素复制到目标对象。
  • 语法:ptr[i] = a.ptr[i];
  • 这是深拷贝的关键步骤——不仅复制指针,而是复制指针指向的内容。

难度: ⭐⭐ 考点: #拷贝构造函数 #深拷贝 #动态内存管理

💡 学习锦囊

📖 相关公式与知识点:

  • 拷贝构造函数参数必须是 const ClassName&(引用,避免无限递归)
  • 深拷贝 = 复制指针指向的内容(而非指针本身)
  • 浅拷贝 = 只复制指针值(两个对象指向同一内存,可能导致重复释放)
  • 当类包含动态分配的内存时,必须自定义拷贝构造函数(深拷贝)

思路分析

类有指针成员 → 需要深拷贝 → 拷贝构造函数参数为 const Array &a → 逐元素复制 ptr[i] = a.ptr[i]

易错点

  • 参数写成 Array a(值传递)——会导致无限递归调用拷贝构造函数
  • 只写 ptr = a.ptr(浅拷贝)——两个对象指向同一内存,析构时重复释放
🔄 举一反三
  1. 如果使用浅拷贝(默认拷贝构造函数),下列代码会出现什么问题?
    cpp
    Array a1(5);
    Array a2 = a1;  // 浅拷贝
    // a1 和 a2 的 ptr 指向同一块内存
    // main 结束时 a1 和 a2 都会调用析构函数 delete[] ptr
    查看练习答案与解析

    答案:程序崩溃(double free) 解析:浅拷贝导致 a1.ptra2.ptr 指向同一块动态内存。当 a1a2 析构时,都会执行 delete[] ptr,同一块内存被释放两次,导致运行时错误(double free)。深拷贝为每个对象分配独立的内存,避免此问题。

三、程序阅读题(每题 6 分,总计 30 分)

  1. 阅读该程序,给出程序的输出结果。
cpp
#include <iostream>
using namespace std;

class Test
{
private:
    int num;
public:
    Test(int n = 0) { num = n; num++; }
    ~Test() { cout << "Destructor is active, number = " << num << endl; }
};

int main()
{
    Test x[2];
    cout << "Exiting main" << endl;
}
查看答案与解析

答案:

Exiting main
Destructor is active, number = 1
Destructor is active, number = 1

解析:

第一步:分析对象数组的构造Test x[2]; 创建了两个 Test 对象,都使用默认参数 n = 0

  • x[0]num = 0,然后 num++num = 1
  • x[1]num = 0,然后 num++num = 1

第二步:分析输出顺序

  • 先执行 cout << "Exiting main" << endl;,输出 Exiting main
  • main 函数结束时,对象数组 x 被销毁,调用析构函数
  • 析构顺序与构造顺序相反:先析构 x[1],再析构 x[0]
  • 两个对象的 num 都是 1,所以输出两次 Destructor is active, number = 1

难度: ⭐⭐ 考点: #析构函数 #对象数组 #构造与析构顺序

💡 学习锦囊

📖 相关公式与知识点:

  • 对象数组的每个元素都会调用构造函数
  • 析构顺序与构造顺序相反(后构造的先析构)
  • 默认参数在创建对象数组时仍然生效
  • 局部对象在离开作用域时自动析构

思路分析

对象数组 → 每个元素独立构造 → 默认参数 n=0num++num=1 → main 结束 → 逆序析构。

易错点

  • 认为 num++num 是 0——num++ 是后置自增,但赋值后 num 确实变为 1
  • 忘记析构顺序是逆序的
🔄 举一反三
  1. 如果将 Test x[2]; 改为 Test x[2] = { Test(5), Test(10) };,输出结果是什么?
    查看练习答案与解析

    答案

    Exiting main
    Destructor is active, number = 11
    Destructor is active, number = 6

    解析x[0]n=5 构造,num=5num++num=6x[1]n=10 构造,num=10num++num=11。析构时逆序:先 x[1](num=11),再 x[0](num=6)。

  1. 阅读该程序,给出程序的输出结果。
cpp
#include <iostream>
using namespace std;

class A
{
public:
    virtual void fun(int data) { cout << "class A:" << data << endl; }
    void fun(char* str) { cout << "class A:" << str << endl; }
};

class B : public A
{
public:
    void fun() { cout << "class B" << endl; }
    void fun(int data) { cout << "class B:" << data << endl; }
    void fun(char* str) { cout << "class B:" << str << endl; }
};

int main()
{
    A* pA;
    B b;
    pA = &b;
    pA->fun(1);
    pA->fun("Hello");
}
查看答案与解析

答案:

class B:1
class A:Hello

解析:

第一步:分析 pA->fun(1) 的调用

  • fun(int) 在基类 A 中声明为 virtual(虚函数)。
  • pAA* 类型,指向 B 对象 b
  • 由于 fun(int) 是虚函数,发生动态绑定,调用 B 中重写的版本。
  • 输出:class B:1

第二步:分析 pA->fun("Hello") 的调用

  • fun(char*) 在基类 A 中没有 virtual 关键字,是普通成员函数。
  • 普通成员函数的调用在编译时确定(静态绑定),根据指针的静态类型 A* 决定。
  • 因此调用 A::fun(char*)
  • 输出:class A:Hello

第三步:关键结论 只有声明为 virtual 的函数才会发生动态绑定(多态)。非虚函数通过基类指针调用时,执行基类版本。


难度: ⭐⭐⭐ 考点: #虚函数 #动态绑定 #静态绑定 #多态

💡 学习锦囊

📖 相关公式与知识点:

  • 虚函数:使用 virtual 关键字声明,支持动态绑定
  • 非虚函数:编译时静态绑定,根据指针/引用的静态类型决定调用版本
  • 动态绑定条件:虚函数 + 基类指针/引用 + 指向派生类对象
  • virtual 关键字只在基类声明时需要,派生类中可写可不写

思路分析

判断函数是否为虚函数 → 是虚函数则动态绑定(调用派生类版本)→ 不是虚函数则静态绑定(调用基类版本)。

易错点

  • 认为所有通过基类指针的调用都会发生多态——只有虚函数才会
  • 混淆 fun(int)fun(char*)——前者是虚函数,后者不是
🔄 举一反三
  1. 如果将 void fun(char* str) 也加上 virtual,输出结果是什么?
    查看练习答案与解析

    答案

    class B:1
    class B:Hello

    解析:两个函数都变成虚函数后,通过基类指针调用时都发生动态绑定,执行 B 类中重写的版本。

  1. 阅读该程序,给出程序的输出结果。
cpp
#include <iostream>
using namespace std;

int main()
{
    cout.fill('*');
    cout.width(10);
    cout << "123.45" << endl;
    cout.width(8);
    cout << "123.45" << endl;
    cout.width(4);
    cout << "123.45" << endl;
}
查看答案与解析

答案:

****123.45
**123.45
123.45

解析:

第一步:理解 cout.fill()cout.width()

  • cout.fill('*'):设置填充字符为 *(默认是空格)。fill() 设置后持续有效,直到被重新设置。
  • cout.width(n):设置下一次输出的最小字段宽度为 n。注意:width() 只对下一次输出有效,之后恢复默认值 0。

第二步:逐行分析

  • cout.width(10):宽度 10,"123.45" 长度为 6,左侧填充 4 个 *****123.45
  • cout.width(8):宽度 8,"123.45" 长度为 6,左侧填充 2 个 *fill('*') 仍然有效)→ **123.45
  • cout.width(4):宽度 4 < "123.45" 长度 6,不进行填充 → 123.45

第三步:关键知识点

  • fill() 设置的填充字符持续有效,不会因为 width() 的重设而恢复默认
  • width() 只对下一次输出有效,输出后自动恢复为 0
  • 当设置宽度小于数据实际宽度时,数据不会被截断

难度: ⭐⭐ 考点: #cout格式化 #width #fill #流操纵

💡 学习锦囊

📖 相关公式与知识点:

  • cout.width(n):设置下次输出的最小宽度,一次性有效
  • cout.fill(ch):设置填充字符,持续有效直到再次设置
  • cout.precision(n):设置浮点数精度
  • cout.setf(ios::left):设置左对齐
  • 当数据实际宽度 ≥ 设置宽度时,不进行填充

思路分析

width() 只对紧接着的一次 << 输出有效,之后自动恢复为 0(不限制宽度)。

易错点

  • 认为 width() 设置后对所有后续输出都有效——实际上只对下一次有效
  • 认为 fill() 设置的填充字符会因 width() 重设而恢复默认——实际上 fill() 持续有效
  • 当设置宽度小于数据实际宽度时,数据不会被截断
🔄 举一反三
  1. 下列代码的输出结果是什么?
    cpp
    cout.fill('0');
    cout.width(5);
    cout << 42 << endl;
    cout.width(5);
    cout << 7 << endl;
    查看练习答案与解析

    答案

    00042
    00007

    解析fill('0') 设置填充字符为 0,持续有效。第一次 width(5) 输出 42(2 位),填充 3 个 000042。第二次 width(5) 输出 7(1 位),填充 4 个 000007

  1. 从键盘输入 10,给出程序的输出结果。
cpp
#include <iostream>
#include <vector>
#include <iterator>
using namespace std;

int main()
{
    vector<int> v;
    int i, n;
    cin >> n;
    for (i = 1; i <= n; i++)
    {
        if (i % 2 != 0)
            v.push_back(i);
    }
    vector<int>::iterator p;
    for (p = v.begin(); p != v.end(); p++)
        cout << *p << " ";
    return 0;
}
查看答案与解析

答案:

1 3 5 7 9

解析:

第一步:理解程序逻辑

  • 输入 n = 10
  • 循环 i 从 1 到 10
  • if (i % 2 != 0):当 i 是奇数时,将 i 添加到 vector 中
  • 因此 vector 中的元素为:1, 3, 5, 7, 9

第二步:分析输出

  • 使用迭代器遍历 vector,输出每个元素后跟一个空格
  • 输出:1 3 5 7 9

第三步:关键知识点

  • vector::push_back():在末尾添加元素
  • vector::begin() / vector::end():获取迭代器
  • i % 2 != 0:判断奇数

难度: ⭐⭐ 考点: #STL #vector #迭代器 #push_back

💡 学习锦囊

📖 相关公式与知识点:

  • vector 是动态数组,支持随机访问
  • push_back(x):在 vector 末尾添加元素
  • begin() 返回指向第一个元素的迭代器
  • end() 返回指向最后一个元素之后位置的迭代器
  • 迭代器遍历:for (p = v.begin(); p != v.end(); p++)

思路分析

输入 10 → 循环 1~10 → 筛选奇数 → push_back → 迭代器遍历输出。

易错点

  • end() 不指向最后一个元素,而是指向最后一个元素之后
  • 循环条件应为 p != v.end(),不是 p <= v.end()
🔄 举一反三
  1. 如果输入 n = 6,输出结果是什么?
    查看练习答案与解析

    答案1 3 5解析:循环 i 从 1 到 6,奇数有 1、3、5,添加到 vector 后输出。

  1. 阅读该程序,给出程序的输出结果。
cpp
#include <iostream>
using namespace std;

class Sample
{
private:
    int i;
    static int count;
public:
    Sample();
    void display();
};

Sample::Sample()
{
    i = 0;
    count++;
}

void Sample::display()
{
    cout << "i=" << i++ << ",count=" << count << endl;
}

int Sample::count = 0;

int main()
{
    Sample a, b;
    a.display();
    b.display();
}
查看答案与解析

答案:

i=0,count=2
i=0,count=2

解析:

第一步:分析静态成员 count

  • count 是静态成员变量,属于类本身,被所有对象共享。
  • 初始值:int Sample::count = 0;
  • 创建 a 时:count++count = 1
  • 创建 b 时:count++count = 2
  • 最终 count = 2

第二步:分析 a.display()

  • a.i = 0(构造函数中初始化为 0)
  • 输出 i=0,然后 i++(后置自增,输出后 a.i 变为 1)
  • count = 2(静态成员,所有对象共享)
  • 输出:i=0,count=2

第三步:分析 b.display()

  • b.i = 0(构造函数中初始化为 0,注意 b.ia.i 是独立的)
  • 输出 i=0,然后 i++(输出后 b.i 变为 1)
  • count = 2(仍然是 2,因为 display 没有修改 count)
  • 输出:i=0,count=2

第四步:关键知识点

  • 静态成员 count 被所有对象共享,构造了两个对象后值为 2
  • 普通成员 i 每个对象独立拥有一份
  • i++ 是后置自增,先输出当前值再自增

难度: ⭐⭐ 考点: #静态成员 #static #后置自增 #对象构造

💡 学习锦囊

📖 相关公式与知识点:

  • 静态成员变量:static int count;——属于类,所有对象共享
  • 静态成员初始化:必须在类外进行,如 int Sample::count = 0;
  • 普通成员变量:每个对象独立拥有一份
  • i++(后置自增):先使用当前值,再自增
  • ++i(前置自增):先自增,再使用新值

思路分析

静态成员 → 所有对象共享 → 构造两次 count=2 → display 中 i++ 是后置 → 输出时 i 还是 0。

易错点

  • 认为 a.display()count 会变化——display() 没有修改 count
  • 混淆 i++++i——后置先输出再自增
🔄 举一反三
  1. 如果将 i++ 改为 ++i,输出结果是什么?
    查看练习答案与解析

    答案

    i=1,count=2
    i=1,count=2

    解析++i 是前置自增,先自增再输出。a.i 从 0 变为 1 后输出,b.i 同理。两次输出 i=1

四、编程题(每题 15 分,总计 30 分)

  1. 按下列要求编程,实现类的定义,并在主函数中测试这个类。

定义一个描述日期的类 Date,包括的数据成员有年(year)、月(month)和日(day),并实现如下功能函数: (1)日期对象初始化; (2)设置日期; (3)以 year/month/day 形式输出日期; (4)判断闰年。

查看答案与解析

答案:

cpp
#include <iostream>
using namespace std;

class Date
{
private:
    int year, month, day;

public:
    // (1) 日期对象初始化(构造函数,带默认参数)
    Date(int y = 1900, int m = 1, int d = 1)
    {
        year = y;
        month = m;
        day = d;
    }

    // (2) 设置日期
    void setDate(int y, int m, int d)
    {
        year = y;
        month = m;
        day = d;
    }

    // (3) 以 year/month/day 形式输出日期
    void outputDate()
    {
        cout << year << "/" << month << "/" << day << endl;
    }

    // (4) 判断闰年
    bool isLeapYear()
    {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }
};

int main()
{
    Date d;                              // 使用默认参数构造
    d.outputDate();                      // 输出默认日期

    d.setDate(2015, 3, 5);              // 设置新日期
    d.outputDate();                      // 输出新日期

    if (d.isLeapYear())
        cout << "a leap year." << endl;
    else
        cout << "not a leap year." << endl;

    return 0;
}

解析:

第一步:设计数据成员 日期需要三个私有数据成员:yearmonthday

第二步:实现构造函数 使用默认参数,方便创建对象时初始化:Date(int y = 1900, int m = 1, int d = 1)

第三步:实现设置日期函数setDate(int y, int m, int d) 修改三个数据成员的值。

第四步:实现输出函数year/month/day 格式输出,使用 / 分隔。

第五步:实现闰年判断 闰年规则:

  • 能被 4 整除但不能被 100 整除,或者
  • 能被 400 整除
  • 表达式:(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)

难度: ⭐⭐ 考点: #类定义 #构造函数 #默认参数 #闰年判断

💡 学习锦囊

📖 相关公式与知识点:

  • 类的基本结构:class ClassName { private: ... public: ... };
  • 构造函数带默认参数:Date(int y = 1900, int m = 1, int d = 1)
  • 闰年判断公式:(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
  • 成员函数在类外定义时使用 ClassName::funcName()

思路分析

类的设计步骤:确定数据成员 → 设计构造函数 → 实现各功能函数 → 编写主函数测试。

易错点

  • 闰年判断条件写错——能被 100 整除但不能被 400 整除的不是闰年(如 1900 年)
  • 忘记在类定义末尾加分号 };
🔄 举一反三
  1. 为 Date 类添加一个成员函数 int getDaysInMonth(),返回当前月份的天数。
    查看练习答案与解析

    答案

    cpp
    int getDaysInMonth()
    {
        int days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        if (month == 2 && isLeapYear())
            return 29;
        return days[month];
    }

    解析:使用数组存储每月天数,2 月特殊处理(闰年 29 天,平年 28 天)。数组索引 0 用 0 占位,方便用月份直接索引。

  1. 根据下列 Vector 类定义,编程完成 Vector 类的具体实现:
cpp
class Vector
{
    friend ostream& operator<<(ostream& out, const Vector& v);
private:
    int* data;
    int size;
public:
    Vector();
    Vector(int a[], int n);
    Vector(const Vector& s);
    ~Vector();
    Vector& operator=(const Vector& v);
    int& operator[](int index);
};
查看答案与解析

答案:

cpp
#include <iostream>
using namespace std;

// 默认构造函数
Vector::Vector()
{
    data = NULL;
    size = 0;
}

// 带参构造函数
Vector::Vector(int a[], int n)
{
    size = n;
    data = new int[size];
    for (int i = 0; i < size; i++)
        data[i] = a[i];
}

// 拷贝构造函数(深拷贝)
Vector::Vector(const Vector& s)
{
    size = s.size;
    data = new int[size];
    for (int i = 0; i < size; i++)
        data[i] = s.data[i];
}

// 析构函数
Vector::~Vector()
{
    if (data != NULL)
        delete[] data;
}

// 赋值运算符重载(深赋值)
Vector& Vector::operator=(const Vector& v)
{
    if (this == &v)
        return *this;
    if (size != v.size)
    {
        delete[] data;
        size = v.size;
        data = new int[size];
    }
    for (int i = 0; i < size; i++)
        data[i] = v.data[i];
    return *this;
}

// 下标运算符重载
int& Vector::operator[](int index)
{
    return data[index];
}

// 流插入运算符重载(友元函数)
ostream& operator<<(ostream& out, const Vector& v)
{
    for (int i = 0; i < v.size; i++)
        out << v.data[i] << " ";
    return out;
}

解析:

第一步:分析类的结构 Vector 类包含两个私有成员:int* data(动态数组指针)和 int size(数组大小)。由于包含指针成员,需要实现深拷贝和深赋值。

第二步:各函数实现要点

函数关键点
默认构造函数data = NULL; size = 0;
带参构造函数分配 new int[n],逐元素复制
拷贝构造函数深拷贝:分配新内存,逐元素复制
析构函数delete[] data 释放动态内存
operator=检查自赋值、释放旧内存、分配新内存、逐元素复制
operator[]返回 data[index] 的引用,支持读写
operator<<友元函数,遍历输出所有元素

第三步:深拷贝与浅拷贝的区别

  • 浅拷贝(默认):data = v.data——两个对象指向同一内存
  • 深拷贝(自定义):分配新内存并复制内容——两个对象独立

第四步:赋值运算符的三步曲

  1. 检查自赋值:if (this == &v) return *this;
  2. 释放旧资源:delete[] data;
  3. 分配新资源并复制:data = new int[size]; + 逐元素复制

难度: ⭐⭐⭐ 考点: #深拷贝 #运算符重载 #动态内存管理 #友元函数

💡 学习锦囊

📖 相关公式与知识点:

  • 深拷贝三要素:拷贝构造 + 赋值运算符 + 析构函数(Rule of Three)
  • 赋值运算符步骤:自赋值检查 → 释放旧资源 → 分配新资源 → 复制数据
  • operator[] 返回引用:支持 v[i] = x; 的赋值操作
  • 友元函数 operator<<:第一个参数是 ostream&,不能是成员函数

思路分析

类有指针成员 → Rule of Three → 拷贝构造(深拷贝)+ 赋值运算符(深赋值)+ 析构函数(释放内存)。

易错点

  • 忘记自赋值检查——v = v; 时可能先释放再访问已释放的内存
  • operator= 忘记释放旧内存——导致内存泄漏
  • operator[] 不返回引用——无法实现 v[i] = x;
🔄 举一反三
  1. 如果 Vector 类增加一个成员函数 void push_back(int val),如何实现?
    查看练习答案与解析

    答案

    cpp
    void push_back(int val)
    {
        int* newData = new int[size + 1];
        for (int i = 0; i < size; i++)
            newData[i] = data[i];
        newData[size] = val;
        delete[] data;
        data = newData;
        size++;
    }

    解析:分配比原来大 1 的新数组 → 复制旧数据 → 添加新元素 → 释放旧数组 → 更新指针和大小。这是动态数组扩容的基本操作。

你正在阅读的是会员专属文档,💕 限时特惠进行中
你尚未登录,目前新用户可获3天体验会员,去登录